ScanでDynamoDBテーブルから1MB以上のデータを取得する方法について
こんにちは、DA部の下地です。
DynamoDBテーブルからScanでデータを取得する際は1回の呼び出しで1MBまでしか取得できません。1MB以上のデータを全件取得するにはScan実行時のレスポンスに含まれているLastEvaluatedKey
を使用してループ処理を実装する必要があります。LastEvaluatedKey
については下記サイトをご参照ください。
今回は、1MB以上のデータをScanで取得しようとした際に全件取得できなかったので改善した実装内容についてまとめます。
1. 実装環境
実行環境は以下のようになっています。
- boto3: 1.12.33
- python: 3.7.0
- CLI: aws-cli/2.0.12
2. 前準備
まずはじめに、下記公式サイトのCloudFormationテンプレートを使用してDynamoDBテーブルを作成します。投入するCSVデータは10万行(11MB)ですので検証に最適です。
2.1 テンプレートの修正
上記の公式サイトのgit(aws-samples/csv-to-dynamodb)からコードをダウンロードします。そして、テンプレートファイルのParameters
にそれぞれDefault値を追加します。
※Defaultを設定するのはこのテンプレートをcliで実装時に簡易的にするためなので本来は不要です
{ "AWSTemplateFormatVersion": "2010-09-09", "Metadata": { }, "Parameters" : { "BucketName": { "Description": "Name of the S3 bucket you will deploy the CSV file to", "Type": "String", "Default": 作成するバケット名, "ConstraintDescription": "must be a valid bucket name." }, "FileName": { "Description": "Name of the S3 file (including suffix)", "Type": "String", "Default": "testfile.csv", "ConstraintDescription": "Valid S3 file name." }, "DynamoDBTableName": { "Description": "Name of the dynamoDB table you will use", "Type": "String", "Default": 作成するDynamoDBテーブル名, "ConstraintDescription": "must be a valid dynamoDB name." } }, "Resources": ...
2.2 スタック作成とデータ投入
修正したテンプレートをAWS CLI
を使用して実行します。
$ aws --profile プロファイル名 cloudformation create-stack --stack-name スタッック名 \ --template-body file://csv-to-dynamodb-master/CloudFormation/CSVToDynamo.template \ --parameters --capabilities CAPABILITY_NAMED_IAM { "StackId": "arn:aws:cloudformation:ap-northeast-1:************:stack/スタッック名/******-***-****-**********" }
環境構築が完了しましたら下記コマンドで作成したS3バケットにファイルをコピーします。コピーするとLambdaが起動しDynamoDBにデータが投入されます。
$ aws --profile プロファイル名 s3 cp csv-to-dynamodb-master/testfile.csv s3://作成するバケット名/ upload: csv-to-dynamodb-master/testfile.csv to s3://作成するバケット名/testfile.csv
3. Scanで1MB以上のデータを取得する
AWS SDK for Python (Boto3)を使用して作成したDynamoDBテーブルにScanを実行し件数の確認を行います。
まずは必要なライブラリのインポートを行います。
from boto3.session import Session session = Session(profile_name= プロファイル名) dynamodb = session.client("dynamodb") table_name = 作成したDynamoDBのテーブル名
3.1 Scan実行
10万行あるDynamoDBテーブルに対してScanを実行します。
# Scan実行 dynamodb_table_counts = dynamodb.scan(TableName=table_name, Select='COUNT') print(dynamodb_table_counts) # 戻り値確認 print(dynamodb_table_counts) {'Count': 4354, 'ScannedCount': 4354, 'LastEvaluatedKey': {'uuid': {'S': '512027108'}}, 'ResponseMetadata': {'RequestId': 'LOF6VVIF2MJS1FUNO5VKUENSG3VV4KQNSO5AEMVJF66Q9ASUAAJG', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'Server', 'date': 'Tue, 26 Jan 2021 14:23:27 GMT', 'content-type': 'application/x-amz-json-1.0', 'content-length': '80', 'connection': 'keep-alive', 'x-amzn-requestid': 'LOF6VVIF2MJS1FUNO5VKUENSG3VV4KQNSO5AEMVJF66Q9ASUAAJG', 'x-amz-crc32': '1948180869'}, 'RetryAttempts': 0}} # 取得件数確認 print(dynamodb_table_counts['Count']) 4354
4354件のレコードしか取れていないことがわかります。
3.2 LastEvaluatedKeyについて
1回のScanでは全件取得できていませんでしたので、2回目のScan開始位置を示すLastEvaluatedKey
について確認します。
# LastEvaluatedKey print(dynamodb_table_counts['LastEvaluatedKey']) {'uuid': {'S': '512027108'}}
3.3 コード修正
取得できたLastEvaluatedKey
値を使用して全件取得できるまでループ処理できるようにコードを追記します。また、Scan実行時にprint文でLastEvaluatedKey
が確認できるようにします。
def get_all_data(): response = dynamodb.scan(TableName=table_name) data = response['Items'] print(response['LastEvaluatedKey']['uuid']) # レスポンスに LastEvaluatedKey が含まれなくなるまでループ処理を実行する while 'LastEvaluatedKey' in response: response = dynamodb.scan(TableName=table_name, ExclusiveStartKey=response['LastEvaluatedKey']) if 'LastEvaluatedKey' in response: print("LastEvaluatedKey: {}".format(response['LastEvaluatedKey'])) data.extend(response['Items']) return data
では作成したコードを実行します。
get_scan_all_data = get_all_data() LastEvaluatedKey: {'uuid': {'S': '512027108'}} LastEvaluatedKey: {'uuid': {'S': '809463059'}} LastEvaluatedKey: {'uuid': {'S': '740823829'}} LastEvaluatedKey: {'uuid': {'S': '449246680'}} LastEvaluatedKey: {'uuid': {'S': '538821303'}} LastEvaluatedKey: {'uuid': {'S': '443480145'}} LastEvaluatedKey: {'uuid': {'S': '719648110'}} LastEvaluatedKey: {'uuid': {'S': '684783685'}} LastEvaluatedKey: {'uuid': {'S': '614555299'}} LastEvaluatedKey: {'uuid': {'S': '601599156'}} LastEvaluatedKey: {'uuid': {'S': '552259385'}} LastEvaluatedKey: {'uuid': {'S': '254441058'}} LastEvaluatedKey: {'uuid': {'S': '981677615'}} LastEvaluatedKey: {'uuid': {'S': '818131101'}} LastEvaluatedKey: {'uuid': {'S': '172728097'}} LastEvaluatedKey: {'uuid': {'S': '333571803'}} LastEvaluatedKey: {'uuid': {'S': '399487626'}} LastEvaluatedKey: {'uuid': {'S': '553535258'}} LastEvaluatedKey: {'uuid': {'S': '655687510'}} LastEvaluatedKey: {'uuid': {'S': '122325742'}} LastEvaluatedKey: {'uuid': {'S': '174549424'}} LastEvaluatedKey: {'uuid': {'S': '782208812'}} print(len(get_scan_all_data)) 100000
10万行の全レコードを取得することができました!
4. 後片付け
最後に検証環境を削除します。S3バケット内にフォルダが存在すると削除できませんのでオブジェクトを削除してスタックを削除します。
#バケット内のファイルを削除 $ aws --profile プロファイル名 s3 rm s3://作成したS3バケット名/testfile.csv delete: s3://作成したS3バケット名/testfile.csv # cfn スタック削除 $ aws --profile プロファイル名 cloudformation delete-stack --stack-name スタッック名
5. まとめ
1MB以上あるDynamoDBテーブルから全件データを取得する方法について実装してきました。少し脱線しましたが環境を作る際にCloudFormationテンプレートを使用できたのは良かったなと思います。同様の問題で悩んでいる方の助けになれば幸いです。